我們在之前的文章有提到,yywrap是在 yylex() 讀取完成後所呼叫的函式,可以在定義後續程式的執行動作。
我們今天將用以下的範例來說明yywrap的運作方式。
給定一篇文章,目標是利用Lex計算出文章的字元數、單詞數與行數。
在這個範例中,我們需要定義三個全域變數來記錄行數,字數與字元數。
%{
int yylex(void);
int nchar, nword, nline;
%}
我們針對以下的情況,對相對應的計數器做更新:
%%
\n { nline++; nchar++; }
[^ \t\n]+ { nword++, nchar += yyleng; }
. { nchar++; }
我們將主程式寫在這裡。
在讀取檔案後,呼叫lex來執行詞語標記。
讀取結束後,印出統計結果。
我們分別在yywrap與主程式的yylex之後分別印出一些文字,看看哪部分會先被執行。
%%
int yywrap(void) {
printf("Characters: %d\nWords: %d\nLines: %d\n", nchar, nword, nline);
return 1;
}
int main(void) {
const char* sFile = "file.txt";
FILE* fp = fopen(sFile, "r");
if (fp == NULL) {
printf("cannot open %s\n", sFile);
return -1;
}
yyin = fp;
yylex();
printf("Main function ends here.\n");
return 0;
}
%{
int yylex(void);
int nchar, nword, nline;
%}
%%
\n { nline++; nchar++; }
[^ \t\n]+ { nword++, nchar += yyleng; }
. { nchar++; }
%%
int yywrap(void) {
printf("Characters: %d\nWords: %d\nLines: %d\n", nchar, nword, nline);
return 1;
}
int main(void) {
const char* sFile = "file.txt";
FILE* fp = fopen(sFile, "r");
if (fp == NULL) {
printf("cannot open %s\n", sFile);
return -1;
}
yyin = fp;
yylex();
printf("Main function ends here.\n");
return 0;
}
Hello, World!
I am a software engineer.
I like lex and yacc.
Characters: 60
Words: 12
Lines: 2
Main function ends here.
答案揭曉:在yylex執行完畢後,會先呼叫yywrap,才接著執行主程式後續的內容。
因此,如果要讀取多個檔案,各檔案的執行流程應該寫在yywrap裡。所有讀取程序完成後的後續步驟則寫在yylex後。
至於如何讀取多個檔案,我們後續會再提到。
💡 由於最後一行沒有換行符號,所以行數統計比實際行數少1行是正常的。
今天我們介紹了yywrap的用法,並且簡單的複習一下從開始到現在的常用lex功能。
我們明天會進入Lex基礎篇的最後一部分 - 錯誤訊息。